local	BUTTON_HEIGHT,	ICON_SIZE,	GAP,	TEXT_OFFSET,	MAX_ENTRIES =
	15,		13,		10,	5

local f = CreateFrame( "Frame", "AraBrokerGuildFriends", UIParent )
local t = CreateFrame"Frame"

local dontShow, block, horde, config, isGuild, tip, tipShown = true
local defaultConfig = { showGuildNotes = true, showGuildName = true, sortType = "class", sortDESC = false, scale = 1 }
local guildEntries, friendEntries, motd, slider, nbEntries = {}, {}
local sliderValue, hasSlider, ShowTablet = 0
local RAID_CLASS_COLORS = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS
local L = {}

local format, strfind, GetGuildRosterInfo, GetFriendInfo, CLASS_BUTTONS, GetDifficultyColor =
	format, strfind, GetGuildRosterInfo, GetFriendInfo, CLASS_BUTTONS, GetQuestDifficultyColor

local orgReloadUI = ReloadUI
ReloadUI = function(...)
	config.reloading = true
	orgReloadUI(...)
end

local tables = {}

local new = function( ... )
	local table = tremove(tables) or {}
	for i=1, select("#",...) do table[i] = select(i,...) end
	return table
end

local del = function( table )
	tables[ #tables + 1 ] = wipe(table)
end


local friendOnline, friendOffline = gsub(ERR_FRIEND_ONLINE_SS,"\124Hplayer:%%s\124h%[%%s%]\124h",""), gsub(ERR_FRIEND_OFFLINE_S,"%%s","")
function f:CHAT_MSG_SYSTEM( event, msg )
	if strfind( msg, friendOnline ) or strfind( msg, friendOffline ) then ShowFriends() end
end

function f:FRIENDLIST_UPDATE()
	for k,v in next,friendEntries do del(v) friendEntries[k]=nil end
	local total, online = GetNumFriends(), 0
	for i = 1, total do
		local name, level, class, zone, connected, status, note = GetFriendInfo(i)
		if connected then
			if online == 0 then
				friendEntries[1] = new( nil, "Name", "", "Lv", "Zone", "Notes" )
			end
			online = online + 1
			friendEntries[online+1] = new( name, status ~= "" and format("%s %s", tostring(status), tostring(name)) or name, L[class], level, zone or "Unknown", note and format("[%s]",note) or "-" )
		end
	end
	f.FriendsBlock.text = format(config.hideTotalFriends and " %d" or " %d/%d", online, total)
	if not isGuild and block then f:FriendsOnEnter(block) end
end

function f:GUILD_ROSTER_UPDATE()
	dontShow = not(isGuild and block)
	f.GuildOnEnter(block)
end

function f:PLAYER_GUILD_UPDATE(event, unit)
	if unit and unit ~= "player" then return end
	return IsInGuild() and GuildRoster()
end

local hordeZones = "Orgrimmar,Undercity,Thunder Bluff,Silvermoon City,Durotar,Tirisfal Glades,The Barrens,Silverpine Forest,Mulgore,The Sepulcher,Eversong Woods,Ghostlands,"
local allianceZones = "Ironforge,Stormwind City,Darnassus,Azuremyst Isle,Bloodmyst Isle,Darkshore,Dun Morogh,Loch Modan,Wetlands,Elwynn Forest,Redridge Mountains,Westfall,Teldrassil,Duskwood,Darshire,Thelsamar,Deeprun Tram,The Exodar,Auberdine,Menethil Harbor,Theramore Isle,"
local function GetZoneColor(zone)
	if strfind( hordeZones   , zone.."," ) then if horde then return 0,1,0 else return 1,0,0 end end
	if strfind( allianceZones, zone.."," ) then if horde then return 1,0,0 else return 0,1,0 end end
	return 1,1,0
end

local function ShowBlockHints()
	local showBelow = UIParent:GetHeight()-f:GetTop()*f:GetScale() < f:GetBottom()*f:GetScale()
	tip:SetOwner(f, "ANCHOR_NONE")
	tip:SetPoint(showBelow and "TOP" or "BOTTOM", f, showBelow and "BOTTOM" or "TOP")
	tip:AddLine"Hints [|cffffffffBlock|r]"
	tip:AddLine("|cffff8020Click|r to open panel.", .2,1,.2)
	tip:AddLine("|cffff8020Ctrl+Click|r to toggle hints.", .2,1,.2)
	tip:AddLine("|cffff8020Shift+RightClick|r to toggle total number.", .2,1,.2)
	if isGuild then
		tip:AddLine("|cffff8020Shift+Click|r to toggle guild name.", .2,1,.2)
		tip:AddLine("|cffff8020RightClick|r to toggle note column.", .2,1,.2)
	else
		tip:AddLine("|cffff8020EverythingElse|r to add a friend.", .2,1,.2)
	end
	tip:Show()
end

local function ShowHints(btn)
	if not config.hideHints and btn and btn.unit then
		local showBelow = UIParent:GetHeight()-f:GetTop()*f:GetScale() < f:GetBottom()*f:GetScale()
		tip:SetOwner(f, "ANCHOR_NONE")
		tip:SetPoint(showBelow and "TOP" or "BOTTOM", f, showBelow and "BOTTOM" or "TOP")
		tip:AddLine"Hints"
		tip:AddLine("|cffff8020Click|r to whisper.", .2,1,.2)
		tip:AddLine("|cffff8020Alt+Click|r to invite.", .2,1,.2)
		tip:AddLine("|cffff8020Shift+Click|r to query guild.", .2, 1, .2)
		if not isGuild or CanEditPublicNote() then tip:AddLine("|cffff8020Ctrl+Click|r to edit note.", .2, 1, .2) end
		if isGuild then
			if CanEditOfficerNote() then tip:AddLine("|cffff8020Ctrl+RightClick|r to edit officer note.", .2, 1, .2) end
			tip:AddLine("|cffff8020RightClick|r to sort closest column.", .2, 1, .2)
		else
			tip:AddLine("|cffff8020MiddleClick|r to remove friend.", .2, 1, .2)
		end
		tip:AddLine("|cffff8020Ctrl+MouseWheel|r to resize tooltip.", .2, 1, .2)
		tip:Show()
		tipShown = true
	end
end

local highlight = f:CreateTexture()
highlight:SetTexture"Interface\\QuestFrame\\UI-QuestTitleHighlight"
highlight:SetBlendMode"ADD"
highlight:SetAlpha(0)

local function Menu_OnEnter(b)
	if b and b.index then
		highlight:SetAllPoints(b)
		if b.index > 1 then
			highlight:SetAlpha(1)
			ShowHints(b)
		end
	end
end

local function Menu_OnLeave(b)
	highlight:ClearAllPoints()
	tip:Hide()
	tipShown = false
	if b and b.index and b.index > 1 then highlight:SetAlpha(0) end
	if not MouseIsOver(f) then
		sliderValue = 0
		block = nil
		f:Hide()
	end
end

local function CreateFS( index, parent, justify, anchor )
	local fs = parent:CreateFontString( nil, "OVERLAY", "SystemFont_Shadow_Med1" )
	fs:SetJustifyH( justify )
	fs:SetTextColor( 1, index == 1 and .8 or 1, 0 )
	if anchor then fs:SetPoint( "LEFT", anchor, "RIGHT", GAP, 0 ) end
	return fs
end

local function EditMOTD()
	f:Hide()
	block = nil
	GuildMOTDEditButton:Click()
end

local cols, colspace = { "class", "name", "level", "zone", "note", "rank" }, { ICON_SIZE }

local function OnGuildmateClick( self, button )
	if not( self and self.unit ) then return end
	if isGuild and button ~= "LeftButton" and not IsModifierKeyDown() then -- sort columns
		local prevDist = GetCursorPosition() / self:GetEffectiveScale() - self:GetLeft()
		local colpos, dist = colspace[1] - prevDist
		for i=2, #colspace do
			colpos = colpos + colspace[i] + (i==2 and TEXT_OFFSET or GAP)
			dist = abs( colpos - colspace[i]*.5 )
			if dist > prevDist then dist, prevDist = i-1, -1 break else prevDist = dist end
		end
		colpos = prevDist < 0 and dist or #colspace
		colpos = not config.showGuildNotes and colpos == 5 and "rank" or cols[colpos]
		if config.sortType == colpos then
			config.sortDESC = not config.sortDESC
		else
			config.sortType, config.sortDESC = colpos, false
		end
		SortGuildRoster( colpos )
	elseif button == "MiddleButton" and not isGuild then
		RemoveFriend( self.unit )
	elseif IsAltKeyDown() then
		InviteUnit( self.unit )
	elseif IsControlKeyDown() then
		if not isGuild then
			FriendsFrame.NotesID = self.index - 1
			StaticPopup_Show( "SET_FRIENDNOTE", self.unit )
		elseif button == "LeftButton" and CanEditPublicNote() or button ~= "LeftButton" and CanEditOfficerNote() then
			SetGuildRosterSelection( guildEntries[self.index][8] )
			StaticPopup_Show( button == "LeftButton" and "SET_GUILDPLAYERNOTE" or "SET_GUILDOFFICERNOTE" )
		end
	else
		SetItemRef("player:"..self.unit, "|Hplayer:"..self.unit.."|h["..self.unit.."]|h", "LeftButton")
	end
end

local function Scroll(self, delta)
	if IsControlKeyDown() then
		config.scale = config.scale - delta * .05
		return ShowTablet(tipShown, isGuild)
	end
	slider:SetValue( sliderValue - delta * (IsModifierKeyDown() and 10 or 3) )
end

local buttons = setmetatable( { }, { __index = function( table, key )
	local button = CreateFrame( "Button", nil, f )
	table[key] = button
	button.index = key
	button:SetNormalFontObject(GameFontNormal)
	button:RegisterForClicks"AnyUp"
	button:SetScript( "OnEnter", Menu_OnEnter )
	button:SetScript( "OnLeave", Menu_OnLeave )

	button:EnableMouseWheel(true)
	button:SetScript( "OnMouseWheel", Scroll)

	button.fontName = CreateFS( key, button, "LEFT" )
	if key == 0 then
		motd = button
		motd.fontName:SetJustifyV"TOP"
		motd.fontName:SetPoint( "TOPLEFT", motd, "TOPLEFT" )
		motd:SetPoint( "TOPLEFT", f, "TOPLEFT", GAP, -GAP )
	else
		button:SetHeight( BUTTON_HEIGHT )
		button.icon = button:CreateTexture()
		button.icon:SetWidth( ICON_SIZE ) button.icon:SetHeight( ICON_SIZE )
		button.icon:SetPoint( "LEFT", button, "LEFT" )

		button.fontName:SetPoint( "LEFT", button.icon, "RIGHT", TEXT_OFFSET, 0 )
		button.fontLevel = CreateFS( key, button, "CENTER", button.fontName )
		button.fontZone  = CreateFS( key, button, "CENTER", button.fontLevel )
		button.fontNotes = CreateFS( key, button, "CENTER", button.fontZone )
		button.fontRank  = CreateFS( key, button, "RIGHT",  button.fontNotes )
	end
	return button
end } )

local function SetButtonData( index, name, class, level, zone, notes, rank, inGroup, isGrouped )
	local button = buttons[index]
	button.fontName:SetText( name or "" )
	if class then
		if index > 1 then
			local color = RAID_CLASS_COLORS[class]
			button.fontName:SetTextColor( color.r, color.g, color.b )
			if inGroup then
				button.icon:SetTexture(isGrouped and "Interface\\Buttons\\UI-CheckBox-Check" or "")
				button.icon:SetTexCoord(.15,.85,.15,.85)
			else
				button.icon:SetTexture"Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes"
				local offset, left, right, bottom, top = 0.025, unpack( CLASS_BUTTONS[class] )
				button.icon:SetTexCoord( left+offset, right-offset, bottom+offset, top-offset )
			end
			color = GetDifficultyColor(level)
			button.fontLevel:SetTextColor( color.r, color.g, color.b )
			button.fontZone:SetTextColor( GetZoneColor(zone or "#") )
		end
		button.fontLevel:SetText( level or "" )
		button.fontZone:SetText( zone or "" )
		button.fontNotes:SetText( notes or "" )
		button.fontRank:SetText( rank or "" )
		return button, button.fontName:GetStringWidth(), button.fontLevel:GetStringWidth(), button.fontZone:GetStringWidth(), button.fontNotes:GetStringWidth(), button.fontRank:GetStringWidth()
	end
	return button, button.fontName:GetStringWidth()
end

local function UpdateScrollButtons(nbEntries)
	local btn = buttons[1]
	for i=2, #buttons do buttons[i]:Hide() end
	for i=2, nbEntries do
		button = buttons[sliderValue+i]
		button:SetPoint("TOPLEFT", btn, "TOPLEFT", 0, BUTTON_HEIGHT*(1-i))
		button:Show()
	end
end

ShowTablet = function( self, _isGuild )
	f:Show()
	isGuild = _isGuild

	f:SetScale(config.scale)
	MAX_ENTRIES = floor( WorldFrame:GetHeight() / ( BUTTON_HEIGHT * f:GetEffectiveScale() ) ) - (config.hideHints and 6 or 12)
	slider:SetHeight(BUTTON_HEIGHT*MAX_ENTRIES)

	local entries = isGuild and guildEntries or friendEntries
	local hasEntries = #entries > 0
	hasSlider = #entries > MAX_ENTRIES

	if not hasEntries then
		entries[1] = new( nil, isGuild and ERR_GUILD_PLAYER_NOT_IN_GUILD or "No friends online.", "" )
	end
	if hasSlider then
		slider:SetMinMaxValues( 0, #entries - MAX_ENTRIES )
		slider:SetValue(sliderValue)
		slider:Show()
	else	slider:Hide() end

	nbEntries = math.min( MAX_ENTRIES, #entries )
	local nameC, levelC, zoneC, notesC, rankC = 0, 0, 0, 0, 0
	local nameW, levelW, zoneW, notesW, rankW
	local hideNotes = isGuild and not config.showGuildNotes
	local extraHeight, button = 0

	local isGrouped = GetNumRaidMembers()>0 and UnitInRaid or GetNumPartyMembers()>0 and UnitInParty or nil
	for i = 1, #entries do
		local entry = entries[i]
		button, nameW, levelW, zoneW, notesW, rankW = SetButtonData( i, entry[2], entry[3], entry[4], entry[5], entry[6], entry[7], isGrouped, isGrouped and entry[1] and isGrouped(entry[1]) )
		button.unit = entry[1]
		button:SetScript( "OnClick", OnGuildmateClick )
		if nameW > nameC then nameC = nameW end
		if levelW and levelW>0 then
			if levelW > levelC then levelC = levelW end
			if  zoneW >  zoneC then  zoneC = zoneW  end
			if notesW > notesC then notesC = notesW end
			if  rankW >  rankC then  rankC = rankW  end
			if hideNotes then button.fontNotes:Hide() else button.fontNotes:Show() end
			button.fontRank:SetPoint( "LEFT", hideNotes and button.fontZone or button.fontNotes, "RIGHT", GAP, 0 )
		end
	end

	if hideNotes then notesC = -GAP end
	local maxWidth = ICON_SIZE + TEXT_OFFSET + nameC + levelC + zoneC + notesC + rankC + GAP * (isGuild and 4 or 3)

	if isGuild and hasEntries then
		SetButtonData( 0, "|cffffffffMOTD:|r  "..GetGuildRosterMOTD() )
		if CanEditMOTD() then motd:SetScript( "OnClick", EditMOTD ) end
		motd.fontName:SetWidth( maxWidth )
		extraHeight = motd.fontName:GetHeight() + BUTTON_HEIGHT
		motd:SetWidth( maxWidth )
		motd:SetHeight( extraHeight )
		motd:Show()
		buttons[1]:SetPoint( "TOPLEFT", motd, "BOTTOMLEFT" )
	else
		buttons[0]:Hide()
		buttons[1]:SetPoint( "TOPLEFT", f, "TOPLEFT", GAP, -GAP )
	end

	UpdateScrollButtons(nbEntries)

	for i=1, #entries do
		button = buttons[i]
		button:SetWidth( maxWidth )
		button.fontName:SetWidth(nameC)
		button.fontLevel:SetWidth(levelC)
		button.fontZone:SetWidth(zoneC)
		button.fontNotes:SetWidth(notesC)
		button.fontRank:SetWidth(rankC)
	end
	colspace[2], colspace[3], colspace[4], colspace[5], colspace[6] = nameC, levelC, zoneC, notesC, rankC

	if hasSlider then slider:SetPoint("TOPRIGHT", buttons[2], "TOPRIGHT", 19 + TEXT_OFFSET, BUTTON_HEIGHT*.5) end
	f:SetWidth( maxWidth + GAP*2 + (hasSlider and 16 + TEXT_OFFSET*2 or 0) )
	f:SetHeight( BUTTON_HEIGHT * nbEntries + extraHeight + GAP*2 )

	block = block or self
	local showBelow = select( 2, block:GetCenter() ) > ( UIParent:GetHeight() / 2 )
	f:ClearAllPoints()
	f:SetPoint( showBelow and "TOP" or "BOTTOM", block, showBelow and "BOTTOM" or "TOP" )
	if not (tipShown or config.hideHints) then ShowBlockHints() end
end


function f:FriendsOnEnter()
	return ShowTablet( self, false )
end

function f:GuildOnEnter()
	for k, v in next, guildEntries do del(v) guildEntries[k]=nil end
	for i=1, GetNumGuildMembers(true) do
		local name, rank, _, level, class, zone, note, offnote, connected, status = GetGuildRosterInfo(i)
		if connected then
			if #guildEntries == 0 then
				guildEntries[1] = new( nil, "Name", "", "Lv", "Zone", "Notes", "Rank" )
			end
			local notes = note ~= "" and (offnote ~= "" and
				format( "[%s] - \124cffff9944[%s]\124r", note, offnote ) or
				format( "[%s]", note )) or (offnote ~= "" and
				format( "\124cffff9944[%s]\124r", offnote ) or "-")
			guildEntries[#guildEntries+1] = new( name, status == "" and name or format( "%s %s", status, name ), L[class], level, zone, notes, rank, i )
		end
	end
	f.GuildBlock.text = IsInGuild() and format(config.hideGuildTotal and" %s%d"or" %s%d/%d", config.showGuildName and GetGuildInfo"player" and GetGuildInfo"player"..": " or "", #guildEntries-1, GetNumGuildMembers(true) ) or "No Guild"
	if dontShow then dontShow = false else return ShowTablet(self, true) end
end


local ldb = LibStub("LibDataBroker-1.1")

f.GuildBlock = ldb:NewDataObject( "|cFFFFB366Ara|r Guild", {
	type = "data source",
	text = GUILD,
	icon = "Interface\\AddOns\\Ara_Broker_Guild_Friends\\guild.tga",
	OnEnter = f.GuildOnEnter,
	OnLeave = Menu_OnLeave,
	OnClick = function(self, button)
		if button == "LeftButton" then
			if IsShiftKeyDown() and IsInGuild() then
				config.showGuildName = not config.showGuildName
				f.GuildBlock.text = format( " %s%d/%d", config.showGuildName and GetGuildInfo"player"..": " or "", #guildEntries-1, GetNumGuildMembers(true) )
			elseif IsControlKeyDown() then
				config.hideHints = not config.hideHints
				ShowTablet( block, isGuild )
			else ToggleFriendsFrame(3) end
		elseif button == "RightButton" and IsShiftKeyDown() then
			config.hideGuildTotal = not config.hideGuildTotal
			f:GuildOnEnter()
		else
			config.showGuildNotes = not config.showGuildNotes
			ShowTablet( block, true )
		end
	end,
} )

f.FriendsBlock = ldb:NewDataObject( "|cFFFFB366Ara|r Friends", {
	type = "data source",
	text = FRIENDS,
	icon = "Interface\\AddOns\\Ara_Broker_Guild_Friends\\friends.tga",
	OnEnter = f.FriendsOnEnter,
	OnLeave = Menu_OnLeave,
	OnClick = function(self, btn)
		if IsControlKeyDown() then
			config.hideHints = not config.hideHints
			ShowTablet( block, isGuild )
		elseif IsShiftKeyDown() then
			config.hideTotalFriends = not config.hideTotalFriends
			f:FRIENDLIST_UPDATE()
		elseif IsModifierKeyDown() or btn~="LeftButton" then
			StaticPopup_Show"ADD_FRIEND"
		else
			ToggleFriendsFrame(1)
		end
	end,
} )

local orgGuildRoster = GuildRoster
GuildRoster = function(...)
	t.guildTimer = 0
	return orgGuildRoster(...)
end

local orgShowFriends = ShowFriends
ShowFriends = function(...)
	t.friendTimer = 0
	return orgShowFriends(...)
end

local function OnUpdate( self, elapsed )
	t.guildTimer = t.guildTimer + elapsed
	if t.guildTimer > 15 then
		if IsInGuild() then GuildRoster() else t.guildTimer = 0 end
	end
	t.friendTimer = t.friendTimer + elapsed
	if t.friendTimer > 15 then ShowFriends() end
end

function f:ADDON_LOADED( event, addon )
	if addon ~= "Ara_Broker_Guild_Friends" then return else f:UnregisterEvent(event) end
	AraBrokerGuildFriendsDB = AraBrokerGuildFriendsDB or defaultConfig
	config = AraBrokerGuildFriendsDB
	for k, v in next, defaultConfig do if config[k]==nil then config[k]=v end end
	if config.reloading then config.reloading = nil else SortGuildRoster(config.sortType) if config.sortDESC then SortGuildRoster(config.sortType) end end

	for eng, loc in next, LOCALIZED_CLASS_NAMES_MALE do
		L[loc] = eng
	end
	for eng, loc in next, LOCALIZED_CLASS_NAMES_FEMALE do
		L[loc] = eng
	end

	tip = GameTooltip
	horde = UnitFactionGroup"player" == "Horde"
	if Skinner then
		Skinner:applySkin(self)
	else
		f:SetBackdrop( { bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
			tile=true, tileSize=12, edgeSize=12, insets = { left=2, right=2, top=2, bottom=2 } } )
		f:SetBackdropColor( 0, 0, 0, .75 )
	end
	f:SetFrameStrata"TOOLTIP"
	f:SetClampedToScreen(true)
	f:EnableMouse(true)

	t:SetScript( "OnUpdate", OnUpdate )
	f:SetScript( "OnEnter", Menu_OnEnter )
	f:SetScript( "OnLeave", Menu_OnLeave )

	f:RegisterEvent"GUILD_ROSTER_UPDATE"
	f:RegisterEvent"PLAYER_GUILD_UPDATE"
	f:RegisterEvent"FRIENDLIST_UPDATE"
	f:RegisterEvent"CHAT_MSG_SYSTEM"

	slider = CreateFrame("Slider", nil, f)
	slider:SetWidth(16)
	slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal")
	slider:SetBackdrop( {
		bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
		edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
		edgeSize = 8, tile = true, tileSize = 8,
		insets = {left = 3, right = 3, top = 6, bottom = 6}
	} )
	slider:SetValueStep(1)
	slider:SetScript( "OnLeave", Menu_OnLeave )
	slider:SetScript( "OnValueChanged", function(self, value)
		if hasSlider then
			sliderValue = value
			if block then UpdateScrollButtons(MAX_ENTRIES) end
		end
	end )

	ShowFriends()
	if IsInGuild(buttons[0]) then GuildRoster() else t.guildTimer = 0 end
	f:GuildOnEnter()
	f.ADDON_LOADED = nil
end

f:RegisterEvent"ADDON_LOADED"
f:SetScript( "OnEvent", function(self, event, ...) return self[event](self, event, ...) end )
